﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PServiceBus.Core.Interface;
using PServiceBus.Core.Security;
using PServiceBus.Gateway.Interfaces.DTO;
using PServiceBus.Core.Runtime.Gateway;
using PServiceBus.Gateway.Interfaces.Exceptions;
using PServiceBus.Core.Runtime;

namespace PServiceBus.Gateway.Interfaces {
    public abstract class GatewayMessageBusBase : IGatewayMessageBus, IDisposable {

        private ServiceRouter _serviceRouter;
        private ServiceInfo _serviceInfo;
        private IGatewayClient _client;
        private bool _disposed;

        protected GatewayMessageBusBase() { }

        protected GatewayMessageBusBase(string address) {
            _serviceRouter = new ServiceRouter(address);
            Connect();
            ESBGateway.ReconnectCallback = Connect;
        }

        private ServiceInfo GetServiceAddress() {
            var services = default(ServiceInfo[]);
            _serviceRouter.Execute(router => services = router.GetServices());
            if (services == null || !services.Any())
                throw new ESBException("There is no available ESB service to connect to at this moment, consider checking that all ESB gateway services are running");
            var totalConnectedClients = services.Min(s => s.TotalConnectedClients);
            var service = services.FirstOrDefault(s => s.TotalConnectedClients == totalConnectedClients);
            return service;
        }

        private void UpdateTotalConnectedClients(bool decrement = true) {
            _serviceRouter.Execute(router =>
                router.SetConnectedClients(_serviceInfo.ID, decrement));
        }

        private void Connect() {
            _serviceInfo = GetServiceAddress();
            if (_client != null) {
                _client.Dispose();
                UpdateTotalConnectedClients();
            }
            var clientGateway = ServiceLocator<IGatewayClient>.Get();
            if (clientGateway == null)
                throw new InvalidOperationException("Could not locate IGatewayClient implementation using ServiceLocator, please define it");

            _client = clientGateway.Init(_serviceInfo.Endpoint);
            ESBGateway.LastEndpoint = new ESBEndpoint()
            {
                Address = _serviceInfo.Endpoint,
                IsAvailable = true
            };
            UpdateTotalConnectedClients(decrement: false);
        }

        public abstract IGatewayMessageBus Init(string address);

        public abstract void CreateSubscriber(string subscriber, Dictionary<string, string> headers);
        public abstract void ChangeSubscriberDurability(string subscriber, bool durable, Dictionary<string, string> headers);
        public abstract void SubscribeTo(string subscriber, string topicName, string filter, bool needHeader, bool caseSensitive, Dictionary<string, string> headers);
        public abstract void UpdateSubscriber(string subscriber, string topicName, string filter, bool needHeader, bool caseSensitive, Dictionary<string, string> headers);
        public abstract void UnSubscribeFrom(string subscriber, string topicName, Dictionary<string, string> headers);
        public abstract void AddTransport(string subscriber, string transportName, ITransport transport, string topicName, Dictionary<string, string> headers);
        public abstract void DeleteTransport(string subscriber, string transportName, Dictionary<string, string> headers);
        public abstract SubscriberInfo SelectSubscriber(string name, Dictionary<string, string> headers);
        public abstract TopicInfo SelectTopic(string name, Dictionary<string, string> headers);
        public abstract void DeleteSubscriber(string name, Dictionary<string, string> headers);
        public abstract void RegisterTopic(TopicInfo topic, Dictionary<string, string> headers);
        public abstract void PublishTopic(TopicInfo topic, Dictionary<string, string> headers);
        public abstract void DeleteTopic(string name, Dictionary<string, string> headers);
        public abstract IList<TopicInfo> GetTopics(Dictionary<string, string> headers);
        public bool IsClosed { get { return _disposed; } }
        public bool ReThrowException { get; set; }
        public ESBIdentity Identity { get; set; }
        public Dictionary<string, string> Headers { get; set; }

        public virtual bool IsValidException(Exception ex) {
            return false;
        }

        protected TResponse Send<TRequest, TResponse>(TRequest request)
            where TRequest : class
            where TResponse : class {
            TResponse response = null;
            try {
                response = _client.Send<TRequest, TResponse>(request);
            } catch (Exception ex) {
                if (ReThrowException || IsValidException(ex)) throw;
            }
            return response;
        }

        protected void Send<TCommand>(TCommand command) where TCommand : class {
            try {
                _client.Send(command);
            } catch (Exception ex) {
                if (ReThrowException || IsValidException(ex)) throw;
            }
        }

        #region IDisposable Members

        public virtual void Dispose() {
            if (_disposed) return;
            try {
                UpdateTotalConnectedClients();
                if (_client != null) _client.Dispose();
                if (_serviceRouter != null) _serviceRouter.Dispose();
                _serviceRouter = null;
                _client = null;
                _disposed = true;
            } catch { }
        }
        #endregion
    }
}
